/*
 * Copyright (c) 2007 Compl Yue Still, All Rights Reserved.
 */

package hbi.wire.java;

import hbi.domain.DomainController;
import hbi.domain.java.JavaDomain;
import hbi.domain.java.JavaGateway;
import hbi.wire.java.host.HostEnd;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class DomainBoundary
{

    protected static final byte[] HBI_VERSION = new byte[] //
    { 'H', 'B', 'I', (byte) 1, (byte) 0 };

    public static final int MAX_WIRE_CONNECTIONS_PER_HANDLER = 1000;

    private final CopyOnWriteArrayList<JavaDomain> domains;

    private final WireAccepter accepter;

    private final List<WireTrafficHandler> trafficHandlers;

    public DomainBoundary(JavaDomain... domains)
    {
        this.domains = new CopyOnWriteArrayList<JavaDomain>();
        this.accepter = new WireAccepter(this);
        this.trafficHandlers = new CopyOnWriteArrayList<WireTrafficHandler>();

        for (JavaDomain d : domains)
            this.domains.add(d);
    }

    public void addLocalDomain(JavaDomain domain)
    {
        domains.add(domain);
    }

    public JavaDomain resolveLocalDomain(String domainTypeName,
        int controllerVersion)
    {
        for (JavaDomain domain : this.domains)
        {
            Class<?> domainType = domain.getFormalDomainType();
            do
            {
                if (domainTypeName.equals(domainType.getCanonicalName()))
                {
                    if (domain.isControllerVersionSupported(controllerVersion))
                        return domain;
                }
                domainType = domainType.getSuperclass();
            }
            while (JavaDomain.class.isAssignableFrom(domainType));
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public <R extends DomainController> Class<R> resolveRemoteDomainType(
        String remoteDomainTypeName, int controllerVersion)
    {
        try
        {
            Class<R> klass = (Class<R>) Class.forName(remoteDomainTypeName,
                true, this.getClass().getClassLoader());
            try
            {
                Method checkMethod = klass.getDeclaredMethod(
                    "acceptRemoteControllerVersion", Integer.TYPE);
                if (!checkMethod.isAccessible()) return null;
                if (!Modifier.isStatic(checkMethod.getModifiers()))
                    return null;
                if (checkMethod.getReturnType() != Boolean.TYPE) return null;
                Boolean supported = (Boolean) checkMethod.invoke(null,
                    controllerVersion);
                if (supported != null && supported.booleanValue())
                    return klass;
            }
            catch (NoSuchMethodException e)
            {
                // treat as supported if no such a method defined
                return klass;
            }
        }
        catch (Exception e)
        {
        }
        return null;
    }

    public void listen(InetSocketAddress localAddr)
    {
        this.accepter.listen(localAddr);
    }

    protected void wireConnected(SocketChannel channel, WireHandshake handshake)
        throws IOException
    {
        WireTrafficHandler handler = null;
        for (WireTrafficHandler h : this.trafficHandlers)
        {
            if (h.getNumChannels() < MAX_WIRE_CONNECTIONS_PER_HANDLER)
            {
                handler = h;
                break;
            }
        }
        if (handler == null)
        {
            handler = new WireTrafficHandler();
            this.trafficHandlers.add(handler);
        }
        JavaGateway<?, ?> gateway = new JavaGateway<JavaDomain, DomainController>(
            (JavaDomain) handshake.getLocalDomain(), handshake
                .getRemoteDomainType());
        HostEnd hostEnd = new HostEnd(gateway, handshake
            .getRequestedLocalDomainControllerVersion());
        handler.handleChannel(channel, hostEnd);
    }

    public void shutdown()
    {
        for (JavaDomain domain : this.domains)
            domain.shutdown();

        if (this.accepter.isAlive()) this.accepter.interrupt();

        for (WireTrafficHandler h : this.trafficHandlers)
            h.interrupt();
        this.trafficHandlers.clear();
    }

}
